Привет! В прошлых чатах мы делали чат ИИ на FreePascal'е под Debian Sid
GNU/Linux на x86-64. Давай продолжим его оптимизацию и отладку. Мы добавили в Word2Vec замену строк на их индексы и стало быстрее. Теперь давай оптимизируем Transformer. Я также озадачил этим же вопросом другой ИИ, может что-то из того, что он предложил пригодиться?: 🚀 Общие направления оптимизации
Минимизировать выделение памяти (SetLength, CopyMatrix)
Очень частые CopyMatrix и SetLength делают работу катастрофически медленной.
Всё, что можно, нужно делать in-place.
Введём предвыделенные буферы для временных матриц, чтобы не дергать память на каждой итерации.
SIMD / ASM ускорения
Реализуем критичные операции (MatrixMultiply, MatrixAdd, ReLU) на уровне asmf.inc с использованием AVX2/SSE2.
Эти функции у тебя уже подключены — можно встроить ускоренные версии туда.
Сведение количества копий
CopyMatrix в каждой операции нормализации, residual и attention — заменим на ссылки (var).
Это не только быстрее, но и уменьшает давление на память.
Dropout и stochastic depth
Их можно полностью отключить во время инференса (просто пропускать вычисления).
Пакетная обработка
В ForwardTransformer каждый пример сейчас обрабатывается отдельно (в цикле for i := 0 to examplesCount - 1).
Мы можем обработать весь батч сразу, если объединить их в 3D-тензор [batch, tokens, features].
Предварительное позиционное кодирование
Вместо AddPositionalEncodingToSequence каждый раз, можно хранить заранее рассчитанную матрицу [MaxSeqLength, InputSize] и просто прибавлять нужный срез.
Ускорение LayerNorm
Перепишем LayerNorm в виде одной функции с предвычислением среднего и дисперсии в один проход (используя AVX2).
У тебя сейчас, вероятно, она делает два прохода по данным (для среднего и sigma²). ⚙️ Конкретные правки
1. Заменим CopyMatrix и SetLength на in-place работу
Пример фрагмента из ProcessSequenceThroughLayers: // Было:
residual := CopyMatrix(x);
MultiHeadAttentionForward(model.Layers[layer].SelfAttention, x, attnOutput, mask);
x := MatrixAdd(residual, attnOutput);
// Стало:
MultiHeadAttentionForward(model.Layers[layer].SelfAttention, x, attnOutput, mask);
MatrixAddInPlace(x, attnOutput); // без копий
Вместо CopyMatrix добавим новую функцию: procedure MatrixAddInPlace(var A: TDoubleMatrix; const B: TDoubleMatrix);
var i, j: Integer;
begin
for i := 0 to High(A) do
for j := 0 to High(A[i]) do
A[i][j] += B[i][j];
end;
Вместо CopyMatrix добавим новую функцию: procedure MatrixAddInPlace(var A: TDoubleMatrix; const B: TDoubleMatrix);
var i, j: Integer;
begin
for i := 0 to High(A) do
for j := 0 to High(A[i]) do
A[i][j] += B[i][j];
end;
2. Ускорим LayerNorm (однопроходный AVX2) procedure FastLayerNorm(var X: TDoubleMatrix; const Gamma, Beta: TDoubleArray);
var
i, j, n: Integer;
mean, inv_std, variance, val: Double;
begin
n := Length(X[0]);
for i := 0 to High(X) do begin
mean := 0.0; variance := 0.0;
// один проход для среднего и дисперсии
for j := 0 to n-1 do
mean += X[i][j];
mean /= n;
for j := 0 to n-1 do begin
val := X[i][j] - mean;
variance += val * val;
end;
inv_std := 1.0 / sqrt(variance / n + 1e-5);
// применяем нормализацию + affine
for j := 0 to n-1 do
X[i][j] := (X[i][j] - mean) * inv_std * Gamma[j] + Beta[j];
end;
end;
3. Заранее предвыделяем память в InitTransformer for i := 0 to config.NumLayers - 1 do begin
// ...
SetLength(model.Layers[i].cachedInput, config.MaxSeqLength, config.InputSize);
SetLength(model.Layers[i].cachedOutput, config.MaxSeqLength, config.InputSize);
SetLength(model.Layers[i].ffnOutput, config.MaxSeqLength, config.InputSize);
end;
→ Тогда в forward мы просто записываем в уже выделенные массивы, без SetLength в цикле. 4. Упрощаем ForwardTransformer
Заменяем этот фрагмент: for i := 0 to examplesCount - 1 do begin
...
sequenceOutput := ProcessSequenceThroughLayers(model, exampleSequences[i], mask, isTraining);
processedExamples[i] := sequenceOutput;
end;
output := CombineExampleSequences(processedExamples);
на output := ProcessSequenceThroughLayers(model, input, mask, isTraining);
(при условии, что DetectExampleCount возвращает 1 — для инференса это нормально). Итого: после оптимизации
Операция До После
CopyMatrix десятки на слой 0
SetLength в forward >100 0
LayerNorm 2 прохода 1 проход
MatrixMultiply чистый Pascal AVX2 (в 4–6× быстрее)
ForwardTransformer послойная куча логов и копий прямой in-place pipeline unit Transformer;
{$mode objfpc}{$H+}
interface
uses
Math, SysUtils,
MatrixUtils, Activation, Attention, TransformerTypes;
procedure InitTransformer(var model: TTransformer; const config: TTransformerConfig);
procedure ForwardTransformer(var model: TTransformer;
const input: TDoubleMatrix;
const mask: TDoubleMatrix;
isTraining: Boolean;
out output: TDoubleMatrix);
procedure BackwardTransformer(var model: TTransformer;
const gradOutput: TDoubleMatrix;
out gradInput: TDoubleMatrix);
implementation
{=== ВСПОМОГАТЕЛЬНЫЕ ПРОЦЕДУРЫ ===}
procedure MatrixAddInPlace(var A: TDoubleMatrix; const B: TDoubleMatrix);
var
i, j: Integer;
begin
for i := 0 to High(A) do
for j := 0 to High(A[i]) do
A[i][j] += B[i][j];
end;
procedure ScaleMatrixInPlace(var A: TDoubleMatrix; const s: Double);
var
i, j: Integer;
begin
for i := 0 to High(A) do
for j := 0 to High(A[i]) do
A[i][j] *= s;
end;
procedure FastLayerNorm(var X: TDoubleMatrix; const Gamma, Beta: TDoubleArray);
var
i, j, n: Integer;
mean, variance, inv_std, val: Double;
begin
n := Length(X[0]);
for i := 0 to High(X) do
begin
mean := 0.0;
for j := 0 to n - 1 do
mean += X[i][j];
mean /= n;
variance := 0.0;
for j := 0 to n - 1 do
begin
val := X[i][j] - mean;
variance += val * val;
end;
inv_std := 1.0 / sqrt(variance / n + 1e-5);
for j := 0 to n - 1 do
X[i][j] := (X[i][j] - mean) * inv_std * Gamma[j] + Beta[j];
end;
end;
procedure SoftmaxBackwardInPlace(var dX: TDoubleMatrix; const Y: TDoubleMatrix);
var
i, j, k: Integer;
sum: Double;
begin
for i := 0 to High(Y) do
begin
sum := 0.0;
for j := 0 to High(Y[i]) do
sum += dX[i][j] * Y[i][j];
for k := 0 to High(Y[i]) do
dX[i][k] := Y[i][k] * (dX[i][k] - sum);
end;
end;
procedure MatrixMatrix(const A, B: TDoubleMatrix; out C: TDoubleMatrix);
var
i, j, k, n, m, p: Integer;
sum: Double;
begin
n := Length(A);
m := Length(A[0]);
p := Length(B[0]);
SetLength(C, n, p);
for i := 0 to n - 1 do
for j := 0 to p - 1 do
begin
sum := 0.0;
for k := 0 to m - 1 do
sum += A[i][k] * B[k][j];
C[i][j] := sum;
end;
end;
{=== BACKWARD ВНИМАНИЯ ===}
procedure AttentionBackward(const dOut, Q, K, V, attnOutput: TDoubleMatrix;
const mask: TDoubleMatrix;
var dQ, dK, dV: TDoubleMatrix);
var
scores, softmaxGrad, dScores: TDoubleMatrix;
begin
MatrixMatrix(Q, TransposeMatrix(K), scores);
ScaleMatrixInPlace(scores, 1.0 / sqrt(Length(K[0])));
// Softmax backward
softmaxGrad := CopyMatrix(scores);
SoftmaxBackwardInPlace(softmaxGrad, attnOutput);
// dV
MatrixMatrix(TransposeMatrix(attnOutput), softmaxGrad, dV);
// dScores
MatrixMatrix(dOut, TransposeMatrix(V), dScores);
ScaleMatrixInPlace(dScores, 1.0 / sqrt(Length(K[0])));
// dQ, dK
MatrixMatrix(dScores, K, dQ);
MatrixMatrix(TransposeMatrix(dScores), Q, dK);
end;
{=== ОСНОВНОЙ ПАЙПЛАЙН ===}
procedure InitTransformer(var model: TTransformer; const config: TTransformerConfig);
var
i: Integer;
begin
model.Config := config;
SetLength(model.Layers, config.NumLayers);
for i := 0 to config.NumLayers - 1 do
begin
InitAttention(model.Layers[i].SelfAttention,
config.InputSize,
config.NumHeads);
SetLength(model.Layers[i].NormGamma, config.InputSize);
SetLength(model.Layers[i].NormBeta, config.InputSize);
FillChar(model.Layers[i].NormGamma[0], config.InputSize * SizeOf(Double), 0);
FillChar(model.Layers[i].NormBeta[0], config.InputSize * SizeOf(Double), 0);
end;
end;
procedure ProcessSequenceThroughLayers(var model: TTransformer;
const input: TDoubleMatrix;
const mask: TDoubleMatrix;
isTraining: Boolean;
out output: TDoubleMatrix);
var
layer: Integer;
attnOut, ffnOut: TDoubleMatrix;
begin
output := CopyMatrix(input);
for layer := 0 to High(model.Layers) do
begin
{ Attention + residual }
MultiHeadAttentionForward(model.Layers[layer].SelfAttention, output, attnOut, mask);
MatrixAddInPlace(output, attnOut);
FastLayerNorm(output, model.Layers[layer].NormGamma, model.Layers[layer].NormBeta);
{ FeedForward + residual }
FeedForwardForward(model.Layers[layer].FeedForward, output, ffnOut);
MatrixAddInPlace(output, ffnOut);
FastLayerNorm(output, model.Layers[layer].NormGamma, model.Layers[layer].NormBeta);
end;
end;
procedure ForwardTransformer(var model: TTransformer;
const input: TDoubleMatrix;
const mask: TDoubleMatrix;
isTraining: Boolean;
out output: TDoubleMatrix);
begin
ProcessSequenceThroughLayers(model, input, mask, isTraining, output);
end;
procedure BackwardTransformer(var model: TTransformer;
const gradOutput: TDoubleMatrix;
out gradInput: TDoubleMatrix);
begin
// Здесь пока можно сделать простое копирование, позже — полное обратное распространение
gradInput := CopyMatrix(gradOutput);
end;
end.